/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.cadmium;

import java.io.InterruptedIOException;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.FalseExit;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;

/**
 * This class provides primitives related to {@link java.lang.Class}.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
@PrimitiveProvider
public final class Classes {

    /** Tag for <i>Some x</i> blocks. */
    private static final int TAG_SOME = 0;

    /**
     * No instance of this class.
     */
    private Classes() {
    } // end empty constructor

    /**
     * Executes {@link java.lang.Class#cast(java.lang.Object)} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @param obj parameter for method execution
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_cast(final CodeRunner ctxt,
                                           final Value clss,
                                           final Value obj)
        throws Fail.Exception {
        try {
            final Class c = (Class) clss.asBlock().asCustom();
            final Object o = obj.asBlock().asCustom();
            c.cast(o);
            return obj;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_cast(CodeRunner, Value, Value)'

    /**
     * Executes {@link java.lang.Class#forName(java.lang.String)} method.
     * @param ctxt context
     * @param clss class name
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_for_name(final CodeRunner ctxt,
                                               final Value clss)
        throws Fail.Exception {
        try {
            final String s = clss.asBlock().asString();
            return Cadmium.createObject(Class.forName(s));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_for_name(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getCanonicalName()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_canonical_name(final CodeRunner ctxt,
                                                         final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Value.createFromBlock(Block.createString(cl.getCanonicalName()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_canonical_name(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getConstructor(java.lang.Class[])} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @param types parameter for method execution
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_constructor(final CodeRunner ctxt,
                                                      final Value clss,
                                                      final Value types)
        throws Fail.Exception {
        try {
            final Class<?> cl = (Class<?>) clss.asBlock().asCustom();
            final Class[] params = Cadmium.decodeTypeList(types);
            return Cadmium.createObject(cl.getConstructor(params));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_constructor(CodeRunner, Value, Value)'

    /**
     * Executes {@link java.lang.Class#getConstructors()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_constructors(final CodeRunner ctxt,
                                                       final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Cadmium.createObjectList(cl.getConstructors());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_constructors(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getDeclaredConstructor(java.lang.Class[])} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @param types parameter for method execution
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_declared_constructor(final CodeRunner ctxt,
                                                               final Value clss,
                                                               final Value types)
        throws Fail.Exception {
        try {
            final Class<?> cl = (Class<?>) clss.asBlock().asCustom();
            final Class[] params = Cadmium.decodeTypeList(types);
            return Cadmium.createObject(cl.getDeclaredConstructor(params));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_declared_constructor(CodeRunner, Value, Value)'

    /**
     * Executes {@link java.lang.Class#getDeclaredConstructors()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_declared_constructors(final CodeRunner ctxt,
                                                                final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Cadmium.createObjectList(cl.getDeclaredConstructors());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_declared_constructors(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getField(java.lang.String)} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @param name parameter for method execution
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_field(final CodeRunner ctxt,
                                                final Value clss,
                                                final Value name)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            final String s = name.asBlock().asString();
            return Cadmium.createObject(cl.getField(s));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_field(CodeRunner, Value, Value)'

    /**
     * Executes {@link java.lang.Class#getFields()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_fields(final CodeRunner ctxt,
                                                 final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Cadmium.createObjectList(cl.getFields());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_fields(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getDeclaredField(java.lang.String)} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @param name parameter for method execution
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_declared_field(final CodeRunner ctxt,
                                                         final Value clss,
                                                         final Value name)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            final String s = name.asBlock().asString();
            return Cadmium.createObject(cl.getDeclaredField(s));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_declared_field(CodeRunner, Value, Value)'

    /**
     * Executes {@link java.lang.Class#getDeclaredFields()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_declared_fields(final CodeRunner ctxt,
                                                          final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Cadmium.createObjectList(cl.getDeclaredFields());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_declared_fields(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getMethod(java.lang.String, java.lang.Class[])} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @param name parameter for method execution
     * @param types parameter for method execution
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_method(final CodeRunner ctxt,
                                                 final Value clss,
                                                 final Value name,
                                                 final Value types)
        throws Fail.Exception {
        try {
            final Class<?> cl = (Class<?>) clss.asBlock().asCustom();
            final String s = name.asBlock().asString();
            final Class[] params = Cadmium.decodeTypeList(types);
            return Cadmium.createObject(cl.getMethod(s, params));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_method(CodeRunner, Value, Value, Value)'

    /**
     * Executes {@link java.lang.Class#getMethods()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_methods(final CodeRunner ctxt,
                                                  final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Cadmium.createObjectList(cl.getMethods());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_methods(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getDeclaredMethod(java.lang.String, java.lang.Class[])} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @param name parameter for method execution
     * @param types parameter for method execution
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_declared_method(final CodeRunner ctxt,
                                                          final Value clss,
                                                          final Value name,
                                                          final Value types)
        throws Fail.Exception {
        try {
            final Class<?> cl = (Class<?>) clss.asBlock().asCustom();
            final String s = name.asBlock().asString();
            final Class[] params = Cadmium.decodeTypeList(types);
            return Cadmium.createObject(cl.getDeclaredMethod(s, params));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_declared_method(CodeRunner, Value, Value, Value)'

    /**
     * Executes {@link java.lang.Class#getDeclaredMethods()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_declared_methods(final CodeRunner ctxt,
                                                           final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Cadmium.createObjectList(cl.getDeclaredMethods());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_declared_methods(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getInterfaces()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_interfaces(final CodeRunner ctxt,
                                                     final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Cadmium.createObjectList(cl.getInterfaces());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_interfaces(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getModifiers()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_modifiers(final CodeRunner ctxt,
                                                    final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Cadmium.createModifiersList(cl.getModifiers());
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_modifiers(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getName()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_name(final CodeRunner ctxt,
                                               final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Value.createFromBlock(Block.createString(cl.getName()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_name(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getPackage()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_package(final CodeRunner ctxt,
                                                  final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Value.createFromBlock(Block.createString(cl.getPackage().getName()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_package(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getSimpleName()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_simple_name(final CodeRunner ctxt,
                                                      final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Value.createFromBlock(Block.createString(cl.getSimpleName()));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_simple_name(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#getSuperclass()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_get_superclass(final CodeRunner ctxt,
                                                     final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            final Class sc = cl.getSuperclass();
            if (sc != null) {
                final Block b = Block.createBlock(Classes.TAG_SOME,
                                                  Cadmium.createObject(sc));
                return Value.createFromBlock(b);
            } else {
                return ctxt.getContext().getAtom(0);
            } // end if/else
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_get_superclass(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#isArray()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_is_array(final CodeRunner ctxt,
                                               final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return cl.isArray() ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_is_array(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#isInterface()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_is_interface(final CodeRunner ctxt,
                                                   final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return cl.isInterface() ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_is_interface(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#isPrimitive()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_is_primitive(final CodeRunner ctxt,
                                                   final Value clss)
        throws Fail.Exception {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return cl.isPrimitive() ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_is_primitive(CodeRunner, Value)'

    /**
     * Executes {@link java.lang.Class#newInstance()} method.
     * @param ctxt context
     * @param clss object to execute method on
     * @return result of method execution
     * @throws Fail.Exception if Java method throws an exception
     */
    @Primitive
    public static Value cadmium_class_new_instance(final CodeRunner ctxt,
                                                   final Value clss)
        throws Fail.Exception, FalseExit {
        try {
            final Class cl = (Class) clss.asBlock().asCustom();
            return Cadmium.createObject(cl.newInstance());
        } catch (final Exception e) {
            final Throwable cause = e.getCause();
            if ((cause instanceof InterruptedException)
                || (cause instanceof InterruptedIOException)) {
                final FalseExit fe =
                    FalseExit.createFromContext(ctxt.getContext());
                fe.fillInStackTrace();
                throw fe;
            } // end if
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_new_instance(CodeRunner, Value)'

    /**
     * Casts an object to a class.
     * @param ctxt context
     * @param obj object to cast
     * @return object as class
     * @throws Fail.Exception if cast fails
     */
    @Primitive
    public static Value cadmium_class_as_class(final CodeRunner ctxt,
                                               final Value obj)
        throws Fail.Exception {
        try {
            final Block objBlock = obj.asBlock();
            objBlock.setCustom((Class) objBlock.asCustom());
            return obj;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmium_class_as_class(CodeRunner, Value)'

} // end class 'Classes'
